/**
 * \file I3AddressBook.cpp
 * \brief This file defines  the I3AddressBook.  I3AddressBook encapsulates a list of
 * DNS name to i3 id mappings stored on disk.
*/

#include <stdio.h>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#include <string>
#include <assert.h>
#include <regex.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/stat.h>
#ifndef _WIN32
#	include <unistd.h>
#endif //_WIN32

extern "C"
{
    #include "i3_debug.h"
    #include "i3.h"
    #include "i3_id.h"
    #include "i3_config.h"
}

#include "I3AddressBook.h"
#include "I3Id.h"

using namespace std;

#include "i3CommonHeaders.h"

/** 
  * This is used in the pthread_create function.
  */
void * dummy_addr_book_refresh(void *arg) {
    I3AddressBook* tmpAddrBook = (I3AddressBook *) arg;
    tmpAddrBook->addr_book_refresh(NULL);
	return NULL;
}

void *I3AddressBook::addr_book_refresh(void *arg)
{
#ifndef _WIN32
  do
  {
    int timeLeft = addr_book_sleeptime;

    do
    {
		timeLeft = sleep(timeLeft);

    } while (timeLeft != 0);

    //init_addrbook(addr_book_file_store);
    
  } while(1);

#else
	Sleep(addr_book_sleeptime * 1000);
	return NULL;
#endif
}

int I3AddressBook::addr_book_lock_mutex()
{
  if ( pthread_mutex_lock(&addr_book_mutex) )
  {
    I3_PRINT_DEBUG0(I3_DEBUG_LEVEL_MINIMAL,"addr_book_lock_mutex: problem with locking mutex\n");
    return 1;
  }
  return 0;
}

int I3AddressBook::addr_book_unlock_mutex()
{
  
  if ( pthread_mutex_unlock(&addr_book_mutex) )
  {
    I3_PRINT_DEBUG0(I3_DEBUG_LEVEL_MINIMAL,"addr_book_unlock_mutex: problem with unlocking mutex\n");
    return 1;
  }

  return 0;
}

// Do we need to read in address book file now?

int I3AddressBook::file_modified()
{

  // Check modificationt time
  struct stat stat_p;
  if ( stat (addr_book_file_store, &stat_p) != 0)
    return 0;

  if ( stat_p.st_mtime > check_time )
  {
    time(&check_time);
    return 1;
  }

  return 0;
}


void I3AddressBook::init_addrbook(char* filename)
{
  // Reading the address book for the first time?
  if ( filename != addr_book_file_store ) {
    time(&check_time);
  } else if ( !file_modified()) {
    return;
  }

  addr_book_lock_mutex();
  if (addrbook != NULL) {
    for (map<string,ID*>::iterator aiter = addrbook->begin(); 
	 aiter != addrbook->end(); aiter++)
      delete aiter->second;
    delete addrbook;
  }
  addrbook = new map<string,ID*>;
  FILE* addrfile = fopen(filename,"r");
  char name[100];
  char nid[200];
  ID *cnid;

  if (addrfile == NULL) {
    I3_PRINT_INFO1(I3_INFO_LEVEL_WARNING,
	       "Error opening address book %s\n",filename);
    addr_book_unlock_mutex();
    exit(-1);
  }

  while(!feof(addrfile)) {
    
    fscanf(addrfile,"%s %s",name,nid);

    if ( !feof(addrfile)) {

      cnid = alloc_i3_id();
      I3Id::convertStr2I3Id (nid, cnid);
      addrbook->insert(make_pair(string(name),cnid));
      
      // Insert I3Id to DNS Name mappings.
      // This is necessary because some I3 option packets like OPT_NOT_FOUND
      // only give the missing trigger id, and not the id of trigger to which
      // the packet was sent.  So this mapping is required for retrieivng the
      // I3VN information.
      string tmpIdStr = I3Id::convertIdToStr(*cnid);
      string *tmpDNSName = new string(name);
      map_ID_DNSName.insert(pair<string, string*>(tmpIdStr, tmpDNSName ));

    } 
  }
  
  fclose(addrfile);
  
  addr_book_unlock_mutex();
  
  if ( filename != addr_book_file_store ) { // happens only first time
    strcpy(addr_book_file_store,filename);
    
    // XXX: TODO: Integrate this thread with coreSelect
    if (pthread_create(&addr_book_refresh_thrd, 
		       NULL,dummy_addr_book_refresh, (void *) this)) {
      I3_PRINT_DEBUG0(I3_DEBUG_LEVEL_MINIMAL, 
		  "Error creating addr book refresh thread !\n");
      return;
    }
  } 
}

/**
  * This function looks up the dns name having with the specified i3 id.
  * @param id The I3 id being reverse looked up
  * @return the DNS name if found, else NULL
  */
string* I3AddressBook::lookupID(ID id) {

    string tmpIdStr = I3Id::convertIdToStr(id);
    //I3_PRINT_DEBUG1(DEBUG_I3_VERBOSE, "Looking for DNS Name  with id %s.\n", sprintf_i3_id(tmpBuf, &id));
    
    map<string, string*>::iterator itr = map_ID_DNSName.find(tmpIdStr);
    
    if (itr != map_ID_DNSName.end()) {
        return  (string*) itr->second;
        
    } else {
            
        return NULL;
    }   
}


ID* I3AddressBook::lookup_addrbook(const char* i3dns)
{
  addr_book_lock_mutex();

  map<string,ID*>::iterator miter = addrbook->find(string(i3dns));
  if ( miter == addrbook->end())
  {
    addr_book_unlock_mutex();
    return NULL;
  }
  ID* rid = duplicate_i3_id(miter->second);
  
  addr_book_unlock_mutex();
  
  return rid;
}


/**
  * Constructs the I3AddressBook and initializes it from the 
  * specified file.
  */
I3AddressBook::I3AddressBook(char *filename) {

    addrbook = NULL;

    addr_book_sleeptime = 10; // 5 mins
    pthread_mutex_init(&addr_book_mutex, NULL);
    init_addrbook(filename);
}



  
  
  
